﻿
namespace TestFsWeb

open System
open System.IO
open System.Net
open System.Web
open System.Text
open Microsoft.FSharp.Control.WebExtensions
open Newtonsoft.Json

open Helper
open HttpListenerExtend
open WebClientExtend
open WebRequestResponseExtend

module DemoPost =

    type Record = {
        Name : string
        Id : int
    }

    type Server(port) =
        do 
            if not HttpListener.IsSupported then
                failwith "HttpListener NOT supported by current OS"

        let m_listener = new HttpListener()
        let m_records = new ResizeArray<Record>()

        do
            m_listener.Prefixes.Add <| sprintf "http://localhost:%d/" port

        let process_post (context: HttpListenerContext) = async {
            let! json = context.Request.AsyncInputString

            let newrecords = JsonConvert.DeserializeObject<Record[]> json
            m_records.AddRange newrecords
            printfn "[SERVER]: %d records are added" newrecords.Length

            context.Response.Close() // send back response with empty body, and dispose
        }

        let process_get (context : HttpListenerContext) = async {
            let json = JsonConvert.SerializeObject(m_records)
            do! context.Response.AsyncReply(json,"application/json")
        }

        let process_delete (context : HttpListenerContext) = 
            printfn "[SERVER]: %d records are deleted" m_records.Count
            m_records.Clear()
            context.Response.Close() // respond with empty body, and dispose

        interface IDisposable with
            member this.Dispose() =
                m_listener.Stop()
                m_listener.Close()

        member this.Start() =
            m_listener.Start()

            let rec loop () = async {
                let! received = m_listener.AsyncGetContext() |> Async.Catch
                match received with
                | Choice2Of2 ex->
                    printfn "Failed to receive request context due to '%s'" ex.Message
                | Choice1Of2 context->
                    match context.Request.HttpMethod.ToLower() with
                    | "post" -> 
                        do! process_post context
                    | "get" -> 
                        do! process_get context
                    | "delete" -> 
                        process_delete context
                    | _ -> 
                        printfn "!!! un-recognized HTTP method" 
                return! loop()
            }

            loop() |> Async.StartImmediate

    type IClient =
        interface
            inherit IDisposable
            abstract AsyncPost: seq<Record> -> Async<string>
            abstract AsyncGet: unit->Async<Record[]>
            abstract AsyncDelete: unit->Async<bool>
        end

    // WebClient doesn't expose API for "delete" method
    type ClientByWebClient(port) =
        let m_client = new WebClient()
        let m_svraddress = sprintf "http://localhost:%d/" port

        interface IClient with
            member this.AsyncPost(records: seq<Record>) = async {
                let json = JsonConvert.SerializeObject records
                return! m_client.AsyncUploadString m_svraddress json
            }

            member this.AsyncGet() = async {
                let! json = m_client.AsyncDownloadString <| new Uri(m_svraddress)
                return JsonConvert.DeserializeObject<Record[]> json
            }

            member this.AsyncDelete() = async {return false;}

        interface IDisposable with
            member this.Dispose() =
                m_client.Dispose()

    type ClientByWebRequest(port) =
        let m_svraddress = sprintf "http://localhost:%d/" port

        interface IClient with
            member this.AsyncPost(records: seq<Record>) = async {
                let json = JsonConvert.SerializeObject records

                let request = WebRequest.Create m_svraddress
                do! request.AsyncPost(json,"application/json")

                return! request.AsyncGetResponseString()
            }

            member this.AsyncGet() = async {
                let request = WebRequest.Create m_svraddress
                let! json = request.AsyncGetResponseString()
                return JsonConvert.DeserializeObject<Record[]> json
            }

            member this.AsyncDelete() = async {
                let request = WebRequest.Create m_svraddress
                request.Method <- "DELETE"
                use! response = request.AsyncGetResponse()
                return (response :?> HttpWebResponse).StatusCode = HttpStatusCode.OK
            }

        interface IDisposable with
            member this.Dispose() =
                ()

module TestDemoPost =
    open DemoPost

    let private record_factory() =
        let seed = ref 0
        fun count -> seq {
            for index = 1 to count do
                incr seed
                yield { Name = sprintf "Xman%d" !seed; Id = !seed }
        }

    let check_post_get (client: IClient) (counts: seq<int>) (factory: int->seq<Record>) =
        async {
            for count in counts do
                let! response = client.AsyncPost <| factory count
                printfn "posted. response: '%s'" response

            let! allrecords = client.AsyncGet()
            allrecords
            |> Seq.iteri (fun index r -> printfn "[%d] name='%s' id=%d" (index+1) r.Name r.Id)   
        }

    let private test (client_maker : int->IClient) =
        let port = 5000
        let factory = record_factory()

        use server = new Server(port)
        server.Start()
        printfn "server is started."

        pause_for("start the client")
        async {
            use client = client_maker port
            do! check_post_get client [|20;30|] factory

            let! deleted = client.AsyncDelete()
            if deleted then
                printfn "**************** DELETE SUCCEEDS, TEST AGAIN ****************"
                do! check_post_get client [|10;6|] factory
            else
                printfn "**************** DELETE NOT SUPPORTED ****************"
        } |> Async.StartImmediate
        
        pause_for("exit")

    let main() =
        // test (fun port -> new ClientByWebClient(port) :> IClient)
        test (fun port -> new ClientByWebRequest(port) :> IClient)



